package edu.pku.sei.mdabase.infrastructure.model;

import java.util.ArrayList;
import java.util.HashMap;

import edu.pku.sei.mdabase.core.ICSPModelManager;
import edu.pku.sei.mdabase.core.IDataType;
import edu.pku.sei.mdabase.core.IModelObject;
import edu.pku.sei.mdabase.infrastructure.event.IEventListener;
import edu.pku.sei.mdabase.infrastructure.event.MTEvent;
import edu.pku.sei.mdabase.infrastructure.graph.Graph;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaClass;



public class ModelManager implements ICSPModelManager, IEventListener{
	@Override
	public int createElement(Object modelType) {
		return 0;
	}
	@Override
	public void deleteElement(int id) {
		
	}
	
	private MModelGraph managedModel;
	
	public ModelManager(MModelGraph model){
		model.addListener(this);
		managedModel = model;
	}

	private HashMap<Integer,MModelObject> id2obj = new HashMap<Integer,MModelObject>();
	private HashMap<MModelObject,Integer> obj2id = new HashMap<MModelObject,Integer>();
	private ArrayList<MModelObject> objs = new ArrayList<MModelObject>();
	
	int count = 0;
	public void addObject(MModelObject obj){
		if(objs.contains(obj))
			return;
		
		id2obj.put(count, obj);
		obj2id.put(obj, count);
		objs.add(obj);
		count++;
		
		resetValues(obj);
	}
	@Override
	public Object getModelFeature(int id, String featureName) {
		MModelObject obj = id2obj.get(id);
		return obj.get(featureName);
	}

	@Override
	public Object getModelObjectByID(int id) {
		if(id==-1) return null;
		return id2obj.get(id);
	}

	private ArrayList<MModelObject> TEMPOBJS = new ArrayList<MModelObject>();

	@Override
	public int[] getValuesOf(Object modelType, Object... param) {
		int[] v = null;
		
		v = type2values.get(modelType);
		
		if(v==null) {
			TEMPOBJS.clear();
			
			for(MModelObject obj : objs){
				IDataType type = (IDataType) obj.getType();
				if(type.isA((IDataType) modelType)){
					TEMPOBJS.add(obj);
				}
			}
			
			if(TEMPOBJS.size()==0)
				v = EMPTY_VALUE;
			else {
				v = new int[TEMPOBJS.size()];
				for(int i=0;i<v.length;i++){
					v[i] = obj2id.get(TEMPOBJS.get(i));
				}				
			}
			type2values.put(modelType, v);
			
		}
		
		assert v!=null;

		return v;
	}
	
	private HashMap<Object,int[]> type2values = new HashMap<Object,int[]>();
	static public int[] EMPTY_VALUE = new int[]{-1};
	
	public void resetValues(MModelObject obj){
		type2values.clear();
		type2instances.put(obj.getType(), null);
	}
	
	@Override
	public void setModelFeatureByID(int mid, int fid, String featureName) {
		MModelObject modelObject = (MModelObject)getModelObjectByID(mid);
		MModelObject featureObject = (MModelObject)getModelObjectByID(fid);
		modelObject.set(featureName, featureObject);
	}
	
	@Override
	public int getModelID(Object modelObject) {
		Integer i = obj2id.get(modelObject);
		
		if(i==null) return -1;
		else return i;
	}
	
	protected boolean deletedObjectById(int id){
		
		Object obj = this.getModelObjectByID(id);
		if(obj!=null){
			this.id2obj.put(id, null);
			this.obj2id.put((MModelObject) obj, null);
			this.objs.remove(obj);
			
			resetValues((MModelObject) obj);
			return true;
		} else return false;
	}
	@Override
	public void receive(Object event) {
		MTEvent mte = (MTEvent)event;
		assert mte.source==managedModel;
		
		if(Graph.GRAPH_NEW_NODE==mte.type){
			this.addObject((MModelObject) mte.data[0]);
		} else if(Graph.GRAPH_NEW_EDGE==mte.type){
			this.addObject((MModelObject) mte.data[0]);
		} else if(Graph.GRAPH_DELETE_NODE==mte.type){
			int i = this.getModelID(mte.data[0]);
			if(i!=-1){
				deletedObjectById(i);
			}
		} else if(Graph.GRAPH_REMOVE_EDGE==mte.type){
			int i = this.getModelID(mte.data[0]);
			if(i!=-1){
				deletedObjectById(i);
			}
		}
	}
	
	public Object[] getInstancesOf(Object modelType){
		Object[] v = null;
		
		v = type2instances.get(modelType);
		
		if(v==null) 
		{
			TEMPOBJS.clear();
			
			for(MModelObject obj : objs)
			{
				if(((IDataType) obj.getType()).isA((IDataType) modelType))
				{
					TEMPOBJS.add(obj);
				}
			}
			
			if(TEMPOBJS.size()==0)
				v = new Object[]{};
			else 
			{
				v = new Object[TEMPOBJS.size()];
				for(int i=0;i<v.length;i++)
				{
					v[i] = TEMPOBJS.get(i);
				}				
			}
			type2instances.put(modelType, v);
			
		}
		
		assert v!=null;

		return v;
	}
	
	private HashMap<Object,Object[]> type2instances = new HashMap<Object,Object[]>();
}
